﻿using System;
using System.Runtime.InteropServices;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Mathematics;
using UnityEngine;

namespace Mars.GpuInstance
{
    public static class MathUtil
    {
        public static Vector4 GetPlane(Vector3 normal, Vector3 point) => new Vector4(normal.x, normal.y, normal.z, -Vector3.Dot(normal, point));

        public static Vector4 GetPlane(Vector3 a, Vector3 b, Vector3 c) => GetPlane(Vector3.Normalize(Vector3.Cross(b - a, c - a)), a);

        private static void GetCameraFarClipPlanePoint(Camera camera)
        {
            if (points == null)
            {
                points = new Vector3[4];
            }
            Transform transform = camera.transform;
            float distance = camera.farClipPlane;
            float halfFovRad = Mathf.Deg2Rad * camera.fieldOfView * 0.5f;
            float upLen = distance * Mathf.Tan(halfFovRad);
            float rightLen = upLen * camera.aspect;
            Vector3 farCenterPoint = transform.position + distance * transform.forward;
            Vector3 up = upLen * transform.up;
            Vector3 right = rightLen * transform.right;
            points[0] = farCenterPoint - up - right; //left-bottom
            points[1] = farCenterPoint - up + right; //right-bottom
            points[2] = farCenterPoint + up - right; //left-up
            points[3] = farCenterPoint + up + right; //right-up
        }

        private static Vector3[] points;
        // 获得摄像头六个面
        public static float4[] GetFrustumPlanes(Camera camera, float4[] planes = null)
        {
            if (planes == null) planes = new float4[6];
            Transform transform = camera.transform;
            Vector3 cameraPosition = transform.position;
            GetCameraFarClipPlanePoint(camera);
            //顺时针
            planes[0] = GetPlane(cameraPosition, points[0], points[2]); //left
            planes[1] = GetPlane(cameraPosition, points[3], points[1]); //right
            planes[2] = GetPlane(cameraPosition, points[1], points[0]); //bottom
            planes[3] = GetPlane(cameraPosition, points[2], points[3]); //up
            planes[4] = GetPlane(-transform.forward, transform.position + transform.forward * camera.nearClipPlane); //near
            planes[5] = GetPlane(transform.forward, transform.position + transform.forward * camera.farClipPlane); //far
            return planes;
        }

        //两个bound是否相交
        public static bool IsIntersect(float2 orgMinPos, float2 orgMaxPos, float2 targetMinPos, float2 targetMaxPos)
        {
            if (IsIntersect(orgMinPos, orgMaxPos, targetMinPos))  //左下
            {
                return true;
            }
            
            if (IsIntersect(orgMinPos, orgMaxPos, targetMaxPos))  //右上
            {
                return true;
            }

            var pos = new float2(targetMinPos.x, targetMaxPos.y);  //左上
            if (IsIntersect(orgMinPos, orgMaxPos, pos)) 
            {
                return true;
            }
            
            pos = new float2(targetMaxPos.x, targetMinPos.y);  //右下
            if (IsIntersect(orgMinPos, orgMaxPos, pos)) 
            {
                return true;
            }
            
            return false;
        }

        //点是否在包围盒内
        public static bool IsIntersect(float2 orgMinPos, float2 orgMaxPos, float2 point)
        {
            return IsIntersect(orgMinPos, orgMaxPos, point.x, point.y);
        }

        public static bool IsIntersect(float2 orgMinPos, float2 orgMaxPos, float x, float z)
        {
            if (orgMinPos.x < x && orgMinPos.y < z && x < orgMaxPos.x && z < orgMaxPos.y)
            {
                return true;
            }
            return false;
        }
        
        public static bool TestPlanesAABB(NativeArray<Plane> planes, Bounds bounds)
        {
            for (int i = 0; i < planes.Length; i++)
            {
                Plane plane = planes[i];
                float3 normal_sign = math.sign(plane.normal);
                float3 test_point = (float3)(bounds.center) + (bounds.extents * normal_sign);
 
                float dot = math.dot(test_point, plane.normal);
                if (dot + plane.distance < 0)  //点在面的外面
                {
                    return false;
                }
            }
 
            return true;
        }
        
        public static int CalculateMipmapLevel(float cameraDis, float uvDistributionMetric, float texelCount, float cameraEyeToScreenDistanceSquared)
        {
            // based on  http://web.cse.ohio-state.edu/~crawfis.3/cse781/Readings/MipMapLevels-Blog.html
            // screenArea = worldArea * (ScreenPixels/(D*tan(FOV)))^2
            // mip = 0.5 * log2 (uvArea / screenArea)
            float dSq = cameraDis * cameraDis;
            if (dSq < 1e-06)
            {
                return 0;
            }

            // uvDistributionMetric is the average of triangle area / uv area (a ratio from world space triangle area to normalised uv area)
            // - triangle area is in world space
            // - uv area is in normalised units (0->1 rather than 0->texture size)

            // m_CameraEyeToScreenDistanceSquared / dSq is the ratio of screen area to world space area

            float v = (texelCount * dSq) / (uvDistributionMetric * cameraEyeToScreenDistanceSquared);
            float desiredMipLevel = 0.5f * Mathf.Log(v, 2);

            //var xx = math.log2(v);

            return (int)desiredMipLevel;
        }
        
        public static float GetCameraEyeToScreenDistanceSquared(Vector3 cameraPosition, float cameraHalfAngle, float screenHalfHeight, float aspectRatio)
        {
            var cameraEyeToScreenDistanceSquared = Mathf.Pow(screenHalfHeight / Mathf.Tan(cameraHalfAngle), 2.0f);

            // Switch to using the horizontal dimension if larger
            if (aspectRatio > 1.0f) // Width is larger than height
            {
                cameraEyeToScreenDistanceSquared *= aspectRatio;
            }

            return cameraEyeToScreenDistanceSquared;
        }
        
        
        public static void Copy<T1, T2>(NativeArray<T1> src, int srcIndex, T2[] dst, int dstIndex, int length)  
            where T1 : struct
            where T2 : struct
        {
            unsafe
            {
                var handle = GCHandle.Alloc(dst, GCHandleType.Pinned);
                var addr = handle.AddrOfPinnedObject();
                var srcAddr = (byte*)src.GetUnsafeReadOnlyPtr();
                UnsafeUtility.MemCpy(
                    (byte*)addr + dstIndex * UnsafeUtility.SizeOf<T2>(),
                    srcAddr + srcIndex * UnsafeUtility.SizeOf<T1>(),
                    length * UnsafeUtility.SizeOf<T2>());
                handle.Free();
            }
        }
    }
}
